package Server;


import java.io.*;
import java.net.*;


/**
 * <p>Title: WebServer.java</p>
 *
 * <p>Description: This is the main class of the server. This is where
 * everything is instantiated and configured. Here is also where multithreading
 * of the server will occur.</p>
 *
 *
 * @Author: YingLuan Tan, Yves Galang
 * @Course: CSC667
 * @Date: 2012 Spring
 * 
 */
public class WebServer
{
	
	/**
	 * Setup the server object for running
	 * 
	 * @param cmdArgs	The command line argument to the server
	 * @throws Exception	When cmd args are invalid
	 */
	public WebServer(String[] cmdArgs) throws Exception
	{
		int size = cmdArgs.length;
		if(size > 0)
			if(cmdArgs[0].startsWith("-"))
			{
				if(!(cmdArgs[0].startsWith("-v") || cmdArgs[0].startsWith("--v")))
					throw new Exception(usage);

				verbose = true;
				if(size > 2)
				{
					httpdPath = cmdArgs[1];
					mimePath = cmdArgs[2];
				}
			}
			else
			{
				if(size > 1)
				{
					httpdPath = cmdArgs[0];
					mimePath = cmdArgs[1];	
				}
			}
		
		_httpdConf = new HttpdConf();
		running = true;
	}
	
	
	
	/**
	 * main function of the server object
	 * reads in the configuration creates threads
	 */
	public void run()
	{
		try{
			_httpdConf.readHttpd(httpdPath);
			_httpdConf.readMIME(mimePath);
			_serverSocket = new ServerSocket(Integer.parseInt(_httpdConf.getSettings("Listen")));
			
			System.out.print("Server started...\r\nListening port " + _httpdConf.getSettings("Listen") + "\r\n");
			
			String tmp = _httpdConf.getSettings("MaxThread");
			final int threadNum = (tmp == null? 8 : Integer.parseInt(tmp));      
			threads = new clientThread[threadNum];

			for(int i = 0; i < threadNum; i++)
			{
				threads[i] = new clientThread(_httpdConf, _serverSocket);
				threads[i].start();
			}

		}
		catch (IOException e)
		{
			System.err.print(e.getMessage() + "\r\n");
			System.err.print("Cannot load configuration files\r\n");
			System.err.print(usage + "\r\n");
			System.exit(1);
		}

	}
	
	/**
	 * Used to kill all the threads by closing the socket
	 * 
	 * @throws IOException
	 */
	private void kill() throws IOException
	{	
		running = false;	
		_serverSocket.close();
	}
	
	private HttpdConf _httpdConf;
	private ServerSocket _serverSocket;
	private clientThread[] threads;
	private String httpdPath = "config/httpd.conf";
	private String mimePath= "config/mime.types";
	private boolean verbose = false;
	private final String usage = "\r\nUsage: [-option] \"path/to/httpd.conf\" \"path/to/mime.type\"\r\nNote: Without setting paths, program defaults to \"./config\" to find config files.";
	private boolean running;
	
	
	
	/**
	 * Private class used for multithreading
	 */
	class clientThread extends Thread
	{

		/**
		 * Constructor used to start a thread.
		 *
		 * @param incoming Socket value which designates the socket used by the
		 * client
		 * @param hcf HttpdConf object created upon server startup.
		 */
		public clientThread(HttpdConf hcf, ServerSocket incoming)
		{
			_request = new Request();
			_response = new Response();                     
		}

		
		/**
		 * Used to run server thread
		 * process requests, return responses and write logs
		 */
		public void run()
		{
			OutputStream output = null;
			while (running)
			{
				try
				{	// keeps listening for new clients, one at a time
					synchronized(_serverSocket)
					{
						if(verbose && running)
							System.out.print(">> " + Thread.currentThread().getName() + " is waiting.....\r\n\r\n");
						
						_socket = _serverSocket.accept(); // waits for client here
						_environment = new Environment(_httpdConf, _request, _socket);
						
						if(verbose)
							System.out.print(">> " + Thread.currentThread().getName() + " accepted the request.\r\n\r\n");
					}
				} 
				catch (IOException e)
				{
					if(e.getMessage().contains("closed"))
						break;		// kill threads by closing the socket
					System.out.println(e.getMessage());
					System.exit(1);
				}

				try
				{	
					output = _socket.getOutputStream();
					_request.parse(_socket.getInputStream());
					_response.processRequest(_request, _httpdConf.getMIME(), _environment, _httpdConf);
					
					_response.writeOutput(_socket.getOutputStream());
					_response.writeToLog(_httpdConf.getSettings("LogFile"), _socket.getInetAddress().getHostName(), _socket.getInetAddress().getHostAddress());
					if(verbose)
					{
						_request.print();
						_response.print();
					}
				} 
				catch (Exception e)
				{
					String errorStr = e.getMessage();
					if(errorStr.contains("Bad Request") || errorStr.contains("Input Stream not ready") || errorStr.contains("Internal Server Error"))
					{
						if(verbose)
							System.out.print( ">> " + e.getMessage() + "\r\n\r\n");
						_response.superBad(errorStr , output);
					}
				}
			}
		}


		private Socket _socket;
		protected Request _request;
		protected Response _response;
		protected Environment _environment;
	}





	/**
	 * program entry point
	 * 
	 * @param args
	 */
	public static void main(String[] args)
	{
		WebServer _server = null;
		try {
			_server = new WebServer(args);
		} 
		catch (Exception e) 
		{
			System.out.print(e.getMessage());
			System.exit(0);
		}
		
		_server.run();
		
		BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
		
		
		try {
			String cmd = "";
			while(true)
			{
				System.out.print("Enter \"kill\" without quotes at anytime to kill the running server\r\n>>");
				cmd = input.readLine().toLowerCase();
				if(cmd.equals("kill"))
				{
					_server.kill();
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}



}

